package com.mx.restful.utils;

import cn.hutool.setting.dialect.Props;
import com.intellij.lang.jvm.annotation.*;
import com.intellij.lang.properties.PropertiesFileType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.file.impl.JavaFileManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.mx.restful.beans.PropertiesKey;
import com.mx.restful.beans.Request;
import com.mx.restful.utils.scanner.SpringHelper;
import com.mx.restful.utils.scanner.YMPHelper;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author: mengxiang.
 * @Date: 2020/6/22.
 * @Time: 11:37 上午.
 * @Description:
 */
public class RestUtil {

    /**
     * 扫描服务端口
     *
     * @param project project
     * @param scope   scope
     * @return port
     */
    public static int scanListenerPort(@NotNull Project project, @NotNull GlobalSearchScope scope) {
        int port = 8080;
        try {
            String value = getConfigurationValue(
                    project, scope,
                    "restful.port"
            );
            if (StringUtils.isNotBlank(value)) {
                port = Integer.parseInt(value);
            }

        } catch (NumberFormatException ignore) {
        }
        return port;
    }

    /**
     * 扫描文字显示
     *
     * @param project project
     * @param scope   scope
     * @return port
     */
    public static boolean scanListenerTitle(@NotNull Project project, @NotNull GlobalSearchScope scope) {
        boolean show = false;

        try {

            String value = getConfigurationValue(
                    project, scope,
                    "restful.show_title"
            );
            if (StringUtils.isNotBlank(value)) {
                show = Boolean.parseBoolean(value);
            }

        } catch (NumberFormatException ignore) {
        }
        return show;
    }

    /**
     * 扫描全局请求头
     *
     * @param project project
     * @param scope   scope
     * @return port
     */
    public static Map<String, String> scanListenerRequestHeaders(@NotNull Project project, @NotNull GlobalSearchScope scope) {
        Map<String, String> headers = new HashMap<>();
        try {
            String headersStr = getConfigurationValue(
                    project, scope,
                    "restful.request_headers"
            );
            if (StringUtils.isBlank(headersStr)) {
                return headers;
            }
            String[] headersStrArray = headersStr.split(",");
            for (String valueStr : headersStrArray) {
                String[] valueArray = valueStr.split("=");
                String key = valueArray[0];
                String value = "";
                if (valueArray.length == 2) {
                    value = valueArray[1];
                }
                headers.put(key, value);
            }
        } catch (NumberFormatException ignore) {
        }
        return headers;
    }


    /**
     * 扫描缓存方式
     *
     * @param project project
     * @param scope   scope
     * @return port
     */
    public static boolean scanCacheType(@NotNull Project project, @NotNull GlobalSearchScope scope) {
        boolean type = false;
        try {
            String value = getConfigurationValue(
                    project, scope,
                    "restful.cache_file"
            );
            if (StringUtils.isNotBlank(value)) {
                type = Boolean.parseBoolean(value);
            }

        } catch (NumberFormatException ignore) {
        }
        return type;
    }

    /**
     * 获取所有的Request
     *
     * @param project project
     * @return map-{key: moduleName, value: itemRequestList}
     */
    @NotNull
    public static Map<String, List<Request>> getAllRequest(@NotNull Project project) {
        return getAllRequest(project, false);
    }

    /**
     * 获取所有的Request
     *
     * @param hasEmpty 是否生成包含空Request的moduleName
     * @param project  project
     * @return map-{key: moduleName, value: itemRequestList}
     */
    @NotNull
    public static Map<String, List<Request>> getAllRequest(@NotNull Project project, boolean hasEmpty) {
        Map<String, List<Request>> map = new HashMap<>();

        Module[] modules = ModuleManager.getInstance(project).getModules();
        for (Module module : modules) {
            List<Request> requests = getAllRequestByModule(project, module);
            if (!hasEmpty && requests.isEmpty()) {
                continue;
            }
            map.put(module.getName(), requests);
        }
        return map;
    }

    /**
     * 获取选中module的所有Request
     *
     * @param project project
     * @param module  module
     * @return list
     */
    @NotNull
    public static List<Request> getAllRequestByModule(@NotNull Project project, @NotNull Module module) {

        List<Request> requests = new ArrayList<>();

        PsiPackage ympPackage = JavaFileManager.getInstance(project).findPackage("net.ymate.platform");
        if (ympPackage != null) {
            //ymp
            List<Request> ympRequestByModule = YMPHelper.getRequestByModule(project, module);
            if (!ympRequestByModule.isEmpty()) {
                requests.addAll(ympRequestByModule);
            }
        } else {
            // Spring
            List<Request> springRequestByModule = SpringHelper.getRequestByModule(project, module);
            if (!springRequestByModule.isEmpty()) {
                requests.addAll(springRequestByModule);
            }
        }

        return requests;
    }

    /**
     * 获取url
     *
     * @param protocol    协议
     * @param port        端口
     * @param contextPath 访问根目录名
     * @param path        路径
     * @return url
     */
    @NotNull
    public static String getRequestUrl(@NotNull String protocol, @Nullable Integer port, @Nullable String contextPath, String path) {
        StringBuilder url = new StringBuilder(protocol + "://");
        url.append("localhost");
        if (port != null) {
            url.append(":").append(port);
        }
        if (contextPath != null && !"null".equals(contextPath) && contextPath.startsWith("/")) {
            url.append(contextPath);
        }
        if (!path.startsWith("/")) {
            url.append("/");
        }
        url.append(path);
        return url.toString();
    }

    public static GlobalSearchScope getModuleScope(@NotNull Module module) {
        return getModuleScope(module, PropertiesKey.scanServiceWithLibrary(module.getProject()));
    }

    protected static GlobalSearchScope getModuleScope(@NotNull Module module, boolean hasLibrary) {
        if (hasLibrary) {
            return module.getModuleWithLibrariesScope();
        } else {
            return module.getModuleScope();
        }
    }

    /**
     * 获取属性值
     *
     * @param attributeValue Psi属性
     * @return {Object | List}
     */
    @Nullable
    public static Object getAttributeValue(JvmAnnotationAttributeValue attributeValue) {
        if (attributeValue == null) {
            return null;
        }
        if (attributeValue instanceof JvmAnnotationConstantValue) {
            return ((JvmAnnotationConstantValue) attributeValue).getConstantValue();
        } else if (attributeValue instanceof JvmAnnotationEnumFieldValue) {
            return ((JvmAnnotationEnumFieldValue) attributeValue).getFieldName();
        } else if (attributeValue instanceof JvmAnnotationArrayValue) {
            List<JvmAnnotationAttributeValue> values = ((JvmAnnotationArrayValue) attributeValue).getValues();
            List<Object> list = new ArrayList<>(values.size());
            for (JvmAnnotationAttributeValue value : values) {
                Object o = getAttributeValue(value);
                if (o != null) {
                    list.add(o);
                } else {
                    // 如果是jar包里的JvmAnnotationConstantValue则无法正常获取值
                    try {
                        Class<? extends JvmAnnotationAttributeValue> clazz = value.getClass();
                        Field myElement = clazz.getSuperclass().getDeclaredField("myElement");
                        myElement.setAccessible(true);
                        Object elObj = myElement.get(value);
                        if (elObj instanceof PsiExpression) {
                            PsiExpression expression = (PsiExpression) elObj;
                            list.add(expression.getText());
                        }
                    } catch (Exception ignore) {
                    }
                }
            }
            return list;
        } else if (attributeValue instanceof JvmAnnotationClassValue) {
            return ((JvmAnnotationClassValue) attributeValue).getQualifiedName();
        }
        return null;
    }

    /**
     * 查找类上的指定注解（包括超类和接口）
     *
     * @param psiClass      PsiClass
     * @param qualifiedName 注解全限定名
     * @return annotation
     */
    @Nullable
    public static PsiAnnotation getClassAnnotation(@NotNull PsiClass psiClass, @NotNull String qualifiedName) {
        Project project = psiClass.getProject();
        GlobalSearchScope scope = psiClass.getResolveScope();
        PsiAnnotation annotation = psiClass.getAnnotation(qualifiedName);
        if (annotation != null) {
            return annotation;
        }
        List<PsiClass> classes = new ArrayList<>();
        classes.add(psiClass.getSuperClass());
        classes.addAll(Arrays.asList(psiClass.getInterfaces()));
        for (PsiClass superPsiClass : classes) {
            if (superPsiClass == null) {
                continue;
            }
            PsiAnnotation classAnnotation = getClassAnnotation(superPsiClass, qualifiedName);
            if (classAnnotation != null) {
                return classAnnotation;
            }
        }
        return null;
    }

    /**
     * 获取方法的所有注解（包括父类）
     *
     * @param psiMethod psiMethod
     * @return annotations
     */
    @NotNull
    public static List<PsiAnnotation> getMethodAnnotations(@NotNull PsiMethod psiMethod) {
        List<PsiAnnotation> annotations = new ArrayList<>(Arrays.asList(psiMethod.getModifierList().getAnnotations()));
        for (PsiMethod superMethod : psiMethod.findSuperMethods()) {
            getMethodAnnotations(superMethod)
                    .stream()
                    // 筛选：子类中方法定义了父类中方法存在的注解时只保留最上层的注解（即实现类的方法注解
                    .filter(annotation -> !annotations.contains(annotation))
                    .forEach(annotations::add);
        }
        return annotations;
    }

    /**
     * 获取扫描到的配置文件
     *
     * @param project project
     * @param scope   scope
     * @param profile ${spring.profiles.active}
     * @return {null | PropertiesFile | YAMLFile}
     */
    @Nullable
    private static File getScanConfigurationFile(@NotNull Project project,
                                                 @NotNull GlobalSearchScope scope,
                                                 @Nullable String profile) {
        // 配置文件全名
        final String configurationFileName = "restful." + PropertiesFileType.DEFAULT_EXTENSION;

        try {
            File file = new File(project.getBasePath() + File.separator + ".restful", configurationFileName);
            if (!file.exists()) {
                return null;
            }
            return file;
        } catch (NoClassDefFoundError e) {
            DumbService.getInstance(project).showDumbModeNotification(String.format(
                    "IDE is missing the corresponding package file: %s",
                    e.getMessage()
            ));
        }
        return null;
    }

    /**
     * 获取properties文件的kv值
     *
     * @param project project
     * @param scope   scope
     * @param name    name
     * @return {value | null}
     */
    @Nullable
    private static String getConfigurationValue(@NotNull Project project,
                                                @NotNull GlobalSearchScope scope,
                                                @NotNull String name) {
        File conf = getScanConfigurationFile(project, scope, null);
        if (conf == null) {
            return null;
        }
        Props props = new Props(conf);
        return props.getStr(name);

    }


}
